 // background.js - MV3 service worker with download queue and start/pause controls

let downloadQueue = [];
let isProcessing = false;
let isPaused = true; // 默认暂停，用户点“开始下载”后才处理队列
let baseSubfolder = ''; // 相对于默认“下载”目录的子文件夹，例如 "XHS/我的视频"
let needFolderPick = false; // 首次开始下载时是否需要选择文件夹
let pickingDownloadId = null; // 正在进行文件夹选择的 downloadId

// Maps a chrome downloadId to our queue item's URL
const downloadIdToUrl = new Map();
// Dedup for immediate downloads
const recentImmediate = new Map(); // url -> timestamp

function seenImmediateRecently(url, windowMs = 6000) {
  try {
    const now = Date.now();
    for (const [u, ts] of Array.from(recentImmediate.entries())) {
      if (now - ts > windowMs) recentImmediate.delete(u);
    }
    const key = String(url || '');
    const ts = recentImmediate.get(key);
    if (ts && now - ts < windowMs) return true;
    recentImmediate.set(key, now);
    return false;
  } catch { return false; }
}

// Load queue, paused state and subfolder from storage on startup
chrome.storage.session.get(['downloadQueue', 'isPaused', 'baseSubfolder'], (result) => {
  if (result.downloadQueue) {
    downloadQueue = result.downloadQueue;
    // Reset status of any in-progress downloads from a previous session
    downloadQueue.forEach(item => {
      if (item.status === 'downloading') {
        item.status = 'queued';
        item.progress = 0;
      }
    });
  }
  if (typeof result.isPaused === 'boolean') {
    isPaused = result.isPaused;
  }
  if (typeof result.baseSubfolder === 'string') {
    baseSubfolder = result.baseSubfolder;
  }
  broadcastQueueUpdate();
  processQueue();
});

function persistQueue() { chrome.storage.session.set({ downloadQueue, isPaused, baseSubfolder }); }

function safeRespond(sendResponse, payload) {
  try { if (typeof sendResponse === 'function') sendResponse(payload); } catch {}
}

function broadcastQueueUpdate() {
  const payload = { type: 'QUEUE_UPDATED', queue: downloadQueue, paused: isPaused, baseSubfolder };
  // 1. Broadcast to popup and other extension pages
  chrome.runtime.sendMessage(payload, () => {
    void chrome.runtime.lastError; // Ignore if no receivers
  });
  // 2. Explicitly send to active tab's content script
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    const tab = tabs && tabs[0];
    if (tab && tab.id) {
      chrome.tabs.sendMessage(tab.id, payload, () => {
        void chrome.runtime.lastError; // Ignore if content script not there
      });
    }
  });
  persistQueue();
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (!message || !message.type) return;

  if (message.type === 'QUEUE_MEDIA') {
    const alreadyExists = downloadQueue.some(item => item.url === message.url);
    if (!alreadyExists) {
      downloadQueue.push({
        url: message.url,
        filename: message.filename || 'untitled',
        mediaType: message.mediaType,
        noteTitle: message.noteTitle,
        status: 'queued', // queued, downloading, completed, error
        progress: 0,
        error: null,
        sender: { tab: sender.tab ? { id: sender.tab.id, url: sender.tab.url } : undefined }
      });
      broadcastQueueUpdate();
      // 不自动开始，等待用户点击开始
      try { sendResponse && sendResponse({ ok: true, added: true }); } catch {}
      return true;
    }
    try { sendResponse && sendResponse({ ok: true, added: false }); } catch {}
    return true;
  }

  if (message.type === 'IMMEDIATE_DOWNLOAD') {
    // Start a one-off download immediately (bypass queue state)
    const url = message.url;
    if (!url) { safeRespond(sendResponse, { ok: false, reason: 'no_url' }); return true; }
    // Skip if already queued/downloading same URL
    if (downloadQueue.some(i => i.url === url && (i.status === 'queued' || i.status === 'downloading'))) {
      safeRespond(sendResponse, { ok: false, reason: 'duplicate_in_queue' });
      return true;
    }
    // Dedup bursts from content (pointerdown/click)
    if (seenImmediateRecently(url)) { safeRespond(sendResponse, { ok: false, reason: 'dedup' }); return true; }
    startImmediateDownload(message).then(() => {}).catch(() => {});
    safeRespond(sendResponse, { ok: true });
    return true;
  }

  if (message.type === 'GET_QUEUE_STATE') {
    sendResponse({ queue: downloadQueue, paused: isPaused, baseSubfolder });
    return true;
  }

  if (message.type === 'CLEAR_COMPLETED_DOWNLOADS') {
    downloadQueue = downloadQueue.filter(item => item.status !== 'completed' && item.status !== 'error');
    broadcastQueueUpdate();
    try { sendResponse && sendResponse({ ok: true }); } catch {}
    return true;
  }

  if (message.type === 'START_QUEUE') {
    isPaused = false;
    if (!baseSubfolder) {
      needFolderPick = true; // 首次下载弹出一次保存对话框以选择目录
    }
    broadcastQueueUpdate();
    processQueue();
    try { sendResponse && sendResponse({ ok: true }); } catch {}
    return true;
  }

  if (message.type === 'PAUSE_QUEUE') {
    isPaused = true;
    broadcastQueueUpdate();
    try { sendResponse && sendResponse({ ok: true }); } catch {}
    return true;
  }

  if (message.type === 'REMOVE_FROM_QUEUE') {
    const url = message.url;
    // 不能删除正在下载的项；允许删除 queued/completed/error
    const currentDownloading = downloadQueue.find(i => i.status === 'downloading');
    if (currentDownloading && currentDownloading.url === url) {
      // 忽略或可考虑标记取消（此处不支持取消正在进行的 chrome.downloads）
      safeRespond(sendResponse, { ok: false, reason: 'item_downloading' });
      return true;
    }
    downloadQueue = downloadQueue.filter(i => i.url !== url);
    broadcastQueueUpdate();
    safeRespond(sendResponse, { ok: true });
    return true;
  }

  if (message.type === 'GET_SETTINGS') {
    sendResponse({ baseSubfolder, paused: isPaused });
    return true;
  }

  if (message.type === 'SET_BASE_SUBFOLDER') {
    baseSubfolder = String(message.baseSubfolder || '').replace(/^\/+|\/+$/g, '');
    broadcastQueueUpdate();
    try { sendResponse && sendResponse({ ok: true }); } catch {}
    return true;
  }
});

async function processQueue() {
  if (isProcessing || isPaused) return;
  // If we are waiting for the first folder selection to complete, don't start another
  if (needFolderPick && pickingDownloadId) return;
  const currentItem = downloadQueue.find(item => item.status === 'queued');
  if (!currentItem) {
    isProcessing = false;
    return;
  }

  isProcessing = true;
  currentItem.status = 'downloading';
  broadcastQueueUpdate();

  try {
    const plainName = currentItem.filename || suggestFilenameFromUrl(currentItem.url, currentItem.noteTitle);
    const suggestedFilename = joinPathSafe(baseSubfolder, plainName);

    chrome.downloads.download(
      {
        url: currentItem.url,
        filename: suggestedFilename,
        conflictAction: 'uniquify',
        saveAs: needFolderPick === true, // 如果需要，让用户选择一次目录
      },
      (downloadId) => {
        if (chrome.runtime.lastError) {
          currentItem.status = 'error';
          currentItem.error = chrome.runtime.lastError.message;
          isProcessing = false;
          broadcastQueueUpdate();
          processQueue(); // Process next
          return;
        }
        downloadIdToUrl.set(downloadId, currentItem.url);
        if (needFolderPick) {
          pickingDownloadId = downloadId;
        }
      }
    );
  } catch (err) {
    currentItem.status = 'error';
    currentItem.error = err.message;
    isProcessing = false;
    broadcastQueueUpdate();
    processQueue(); // Process next
  }
}

// Immediate download helper (does not mutate queue)
async function startImmediateDownload({ url, filename, noteTitle }) {
  return new Promise((resolve) => {
    chrome.downloads.download(
      {
        url,
        filename: filename || suggestFilenameFromUrl(url, noteTitle),
        conflictAction: 'uniquify',
        saveAs: false,
      },
      () => {
        // Ignore errors for immediate flow; user has toast in content
        resolve();
      }
    );
  });
}

chrome.downloads.onChanged.addListener((delta) => {
  // If we are in the one-time folder picking flow, capture the chosen folder
  if (pickingDownloadId && delta.id === pickingDownloadId && delta.filename && delta.filename.current) {
    try {
      const full = String(delta.filename.current);
      const lower = full.toLowerCase();
      const markers = ['\\downloads\\', '/downloads/', '\\下载\\', '/下载/'];
      let sub = '';
      for (const m of markers) {
        const idx = lower.lastIndexOf(m);
        if (idx >= 0) {
          const after = full.substring(idx + m.length);
          const parts = after.split(/[/\\]/);
          parts.pop();
          sub = parts.filter(Boolean).join('/');
          break;
        }
      }
      if (!sub) {
        const partsAbs = full.split(/[/\\]/);
        if (partsAbs.length >= 2) sub = partsAbs[partsAbs.length - 2];
      }
      baseSubfolder = (sub || '').replace(/^\/+|\/+$/g, '');
      needFolderPick = false;
      pickingDownloadId = null;
      persistQueue();
      broadcastQueueUpdate();
      // Now that folder is chosen, resume queue silently
      processQueue();
    } catch {}
  }
  const url = downloadIdToUrl.get(delta.id);
  if (!url) return;

  const item = downloadQueue.find(i => i.url === url && i.status === 'downloading');
  if (!item) return;

  if (delta.totalBytes && delta.totalBytes.current > 0) {
    item.progress = Math.round((delta.bytesReceived.current / delta.totalBytes.current) * 100);
  }

  if (delta.state) {
    switch (delta.state.current) {
      case 'complete':
        item.status = 'completed';
        item.progress = 100;
        downloadIdToUrl.delete(delta.id);
        isProcessing = false;
        processQueue(); // Process next
        break;
      case 'interrupted':
        item.status = 'error';
        item.error = delta.error ? delta.error.current : 'Download interrupted';
        downloadIdToUrl.delete(delta.id);
        isProcessing = false;
        processQueue(); // Process next
        break;
    }
  }
  broadcastQueueUpdate();
});

chrome.downloads.onDeterminingFilename.addListener((item, suggest) => {
  const queueItem = downloadQueue.find(i => i.url === item.url);

  // 统一推断最终文件名（无论是否在队列中）
  let finalName;
  if (queueItem) {
    // 队列项：视频强制 mp4，其它按 MIME/URL 猜测
    let ext = '';
    if (queueItem.mediaType === 'video') {
      ext = '.mp4';
    } else {
      ext = guessExtFromMime(item.mime) || extFromUrl(item.url) || '.bin';
    }
    const baseNameNoExt = (queueItem.filename || 'download').replace(/\.[^/.]+$/, "");
    finalName = baseNameNoExt + ext;
    queueItem.filename = finalName;
    broadcastQueueUpdate();
  } else {
    // 立即下载或非队列：沿用原始文件名但修正扩展名，避免 .txt/.html
    const inferredExt = guessExtFromMime(item.mime) || extFromUrl(item.url) || fallbackExtByHeuristics(item.url) || '.bin';
    const baseNameNoExt = String(item.filename || 'download').replace(/\.[^/.]+$/, "");
    finalName = baseNameNoExt + inferredExt;
  }

  const withFolder = joinPathSafe(baseSubfolder, finalName);
  suggest({ filename: withFolder, conflictAction: 'uniquify' });
  return true;
});

function suggestFilenameFromUrl(url, title) {
  try {
    const u = new URL(url);
    const pathname = u.pathname;
    let base = pathname.split('/').filter(Boolean).pop() || 'media';
    base = base.split('?')[0].split('#')[0];
    const ext = extFromUrl(url) || fallbackExtByHeuristics(url);
    const name = title ? title.replace(/[\\/:*?"<>|]/g, '_') : base.replace(/\.[^/.]+$/, "");
    return `${name}${ext}`;
  } catch (e) {
    return `media${fallbackExtByHeuristics(url)}`;
  }
}

function guessExtFromMime(mime) {
  if (!mime) return '';
  const mimeMap = {
    'image/jpeg': '.jpg',
    'image/png': '.png',
    'image/webp': '.webp',
    'image/gif': '.gif',
    'image/avif': '.avif',
    'video/mp4': '.mp4',
    'video/webm': '.webm',
    'video/quicktime': '.mov',
  };
  return mimeMap[mime] || '';
}

function extFromUrl(url) {
  try {
    const u = new URL(url);
    let path = u.pathname.split('!')[0];
    const m = path.match(/\.([a-z0-9]{2,6})$/i);
    if (m) return `.${m[1].toLowerCase()}`;
    return '';
  } catch {
    return '';
  }
}

function fallbackExtByHeuristics(url) {
  const lower = (url || '').toLowerCase();
  if (lower.includes('video')) return '.mp4';
  if (lower.includes('image')) return '.jpg';
  if (lower.includes('xhscdn')) return '.jpg';
  return '.bin';
}

function joinPathSafe(folder, name) {
  const safeFolder = String(folder || '').replace(/[\\:*?"<>|]/g, '_').replace(/^\/+|\/+$/g, '');
  const safeName = String(name || 'download').replace(/[\\:*?"<>|]/g, '_');
  return safeFolder ? `${safeFolder}/${safeName}` : safeName;
}
